package com.ceair.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ceair.authorization.wechat.WechatUserRequestEntityConverter;
import com.ceair.authorization.wechat.WechatUserResponseConverter;
import com.ceair.entity.Oauth2ThirdAccount;
import com.ceair.entity.SysAuthority;
import com.ceair.entity.SysRoleAuthority;
import com.ceair.entity.SysUserRole;
import com.ceair.entity.model.security.CustomGrantedAuthority;
import com.ceair.exception.InvalidCaptchaException;
import com.ceair.mapper.SysAuthorityMapper;
import com.ceair.mapper.SysRoleAuthorityMapper;
import com.ceair.mapper.SysUserRoleMapper;
import com.ceair.service.IOauth2ThirdAccountService;
import com.ceair.strategy.context.Oauth2UserConverterContext;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.ResourceHttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.oauth2.client.http.OAuth2ErrorResponseErrorHandler;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.WebAttributes;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author wangbaohai
 * @ClassName CustomOauth2UserService
 * @description: 自定义三方oauth2登录获取用户信息服务
 * @date 2024年11月26日
 * @version: 1.0.0
 */
@Service
public class CustomOauth2UserService extends DefaultOAuth2UserService {

    private final IOauth2ThirdAccountService thirdAccountService;

    private final Oauth2UserConverterContext userConverterContext;

    private final SysUserRoleMapper sysUserRoleMapper;
    private final SysRoleAuthorityMapper sysRoleAuthorityMapper;
    private final SysAuthorityMapper sysAuthorityMapper;

    public CustomOauth2UserService(IOauth2ThirdAccountService thirdAccountService,
                                   Oauth2UserConverterContext userConverterContext, SysUserRoleMapper sysUserRoleMapper, SysRoleAuthorityMapper sysRoleAuthorityMapper, SysAuthorityMapper sysAuthorityMapper) {
        this.thirdAccountService = thirdAccountService;
        this.userConverterContext = userConverterContext;
        this.sysUserRoleMapper = sysUserRoleMapper;
        this.sysRoleAuthorityMapper = sysRoleAuthorityMapper;
        this.sysAuthorityMapper = sysAuthorityMapper;
        // 初始化时添加微信用户信息请求处理，oidcUserService本质上是调用该类获取用户信息的，不用添加
        super.setRequestEntityConverter(new WechatUserRequestEntityConverter());
        // 设置用户信息转换器
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());
        List<HttpMessageConverter<?>> messageConverters = List.of(
                new StringHttpMessageConverter(),
                // 获取微信用户信息时使其支持“text/plain”
                new WechatUserResponseConverter(),
                new ResourceHttpMessageConverter(),
                new ByteArrayHttpMessageConverter(),
                new AllEncompassingFormHttpMessageConverter()
        );
        restTemplate.setMessageConverters(messageConverters);
        super.setRestOperations(restTemplate);
    }

    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        try {
            OAuth2User oAuth2User = super.loadUser(userRequest);
            // 转为项目中的三方用户信息
            Oauth2ThirdAccount oauth2ThirdAccount = userConverterContext.convert(userRequest, oAuth2User);
            // 检查用户信息
            thirdAccountService.checkAndSaveUser(oauth2ThirdAccount);
            // 将loginType设置至attributes中
            LinkedHashMap<String, Object> attributes = new LinkedHashMap<>(oAuth2User.getAttributes());
            // 将RegistrationId当做登录类型
            attributes.put("loginType", userRequest.getClientRegistration().getRegistrationId());
            // 将用户ID放到唯一键就兼容本系统用户和三方系统用户了
            attributes.put("uniqueId", oauth2ThirdAccount.getUniqueId());
            String userNameAttributeName =
                    userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint()
                    .getUserNameAttributeName();

            // 通过用户角色关联表查询对应的角色
            List<SysUserRole> userRoles =
                    sysUserRoleMapper.selectList(Wrappers.lambdaQuery(SysUserRole.class).eq(SysUserRole::getUserId,
                            oauth2ThirdAccount.getUserId()));
            List<Long> rolesId =
                    Optional.ofNullable(userRoles).orElse(Collections.emptyList()).stream().map(SysUserRole::getRoleId).toList();

            // 如果没有角色，则直接返回
            if (rolesId.isEmpty()) {
                return new DefaultOAuth2User(oAuth2User.getAuthorities(), attributes, userNameAttributeName);
            } else {
                // 如果有就查询权限
                // 通过角色菜单关联表查出对应的菜单
                List<SysRoleAuthority> roleMenus =
                        sysRoleAuthorityMapper.selectList(Wrappers.lambdaQuery(SysRoleAuthority.class).in(SysRoleAuthority::getRoleId, rolesId));
                List<Long> menusId =
                        Optional.ofNullable(roleMenus).orElse(Collections.emptyList()).stream().map(SysRoleAuthority::getAuthorityId).toList();

                // 如果没有菜单，则直接返回
                if (menusId.isEmpty()) {
                    return new DefaultOAuth2User(oAuth2User.getAuthorities(), attributes, userNameAttributeName);
                } else {
                    // 根据菜单ID查出菜单
                    List<SysAuthority> menus = sysAuthorityMapper.selectByIds(menusId);
                    Set<CustomGrantedAuthority> authorities =
                            Optional.ofNullable(menus).orElse(Collections.emptyList()).stream().map(SysAuthority::getAuthority).map(CustomGrantedAuthority::new).collect(Collectors.toSet());

                    // 如果没有权限，则直接返回
                    if (authorities.isEmpty()) {
                        return new DefaultOAuth2User(oAuth2User.getAuthorities(), attributes, userNameAttributeName);
                    } else {
                        // 使用本系统的权限覆盖三方系统权限
                        return new DefaultOAuth2User(authorities.stream().toList(), attributes, userNameAttributeName);
                    }
                }
            }
        } catch (Exception e) {
            // 获取当前request
            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
            if (requestAttributes == null) {
                throw new InvalidCaptchaException("Failed to get the current request.");
            }
            HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
            // 将异常放入session中
            request.getSession(Boolean.FALSE).setAttribute(WebAttributes.AUTHENTICATION_EXCEPTION, e);
            throw new AuthenticationServiceException(e.getMessage(), e);
        }
    }

}
